using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

namespace BLT
{
    #region Namespace type definitions
    public enum MouseButtons { leftButton, middleButton, rightButton, XButton1, XButton2, None };
    public enum MouseAnalogs { Pointer, ScrollWheel };
    public enum GamePadAnalogs { LeftStick, RightStick, LeftTrigger, RightTrigger };
    #endregion

    public class InputHandler
    {

        #region Type Definitions
        /// <summary>
        /// The delegate function signature that is used for all Button mappings
        /// <param name="button">The button that was activated</param>
        /// <param name="state">The state of the Button (pressed or released)</param>
        /// </summary>
        public delegate void ButtonInputAction(Buttons button, ButtonState state, PlayerIndex index);

        /// <summary>
        /// The delegate function signature that is used for all GamePad Thumbstick/Trigger mappings
        /// <param name="x">The "X" value of the GamePad thumbstick control</param>
        /// <param name="y">The "Y" value of the GamePad thumbstick control</param>
        /// </summary>
        public delegate void GamePadInputAction(GamePadAnalogs control, float x, float y, PlayerIndex index);

        /// <summary>
        /// The delegate function signature that is used for all Mouse mappings
        /// <param name="x">The "X" value of the mouse control (always 0 for the scrollwheel)</param>
        /// <param name="y">The "Y" value of the mouse control</param>
        /// </summary>
        public delegate void MouseInputAction(MouseAnalogs control, int x, int y);

        public delegate void KeyboardInputAction(Keys key, KeyState state);

        public delegate void MouseButtonInputAction(MouseButtons button, ButtonState state);
        #endregion

        #region Fields
        private KeyboardState m_oldKeyboard = Keyboard.GetState();
        private MouseState m_oldMouse = Mouse.GetState();
        private GamePadState m_oldGamePad = GamePad.GetState(PlayerIndex.One);
        private GamePadState m_oldGamePad2 = GamePad.GetState(PlayerIndex.Two);
        private GamePadState m_oldGamePad3 = GamePad.GetState(PlayerIndex.Three);
        private GamePadState m_oldGamePad4 = GamePad.GetState(PlayerIndex.Four);

        //Lists for holding all the bindings
        private List<KeyValuePair<Buttons, ButtonInputAction>> m_gamePadBindings = new List<KeyValuePair<Buttons, ButtonInputAction>>();
        private List<KeyValuePair<Keys, KeyboardInputAction>> m_keyboardBindings = new List<KeyValuePair<Keys, KeyboardInputAction>>();
        private List<KeyValuePair<MouseButtons, MouseButtonInputAction>> m_mouseBindings = new List<KeyValuePair<MouseButtons, MouseButtonInputAction>>();
        private List<KeyValuePair<MouseAnalogs, MouseInputAction>> m_mouseAnalogBindings = new List<KeyValuePair<MouseAnalogs, MouseInputAction>>();
        private List<KeyValuePair<GamePadAnalogs, GamePadInputAction>> m_gamePadAnalogBindings = new List<KeyValuePair<GamePadAnalogs, GamePadInputAction>>();
        #endregion

        #region Other Functions
        /// <summary>
        /// Bind a gamepad button to an action
        /// <param name="button">the button to associate with an action</param>
        /// <param name="action">The action to take when this button is pressed</param>
        /// <param name="flag">0 to overwrite, 1 to append a binding</param>
        /// </summary>
        public void Bind(Buttons button, ButtonInputAction action, int flag)
        {
            //  Remove all old bindings if the flag is set.
            if (flag == 0)
                for (int i = 0; i < m_gamePadBindings.Count; i++)
                    if (m_gamePadBindings[i].Key == button)
                    {
                        m_gamePadBindings.RemoveAt(i);
                        i--;
                    }
            //  Add/append the new binding.
            m_gamePadBindings.Add(new KeyValuePair<Buttons, ButtonInputAction>(button, action));
        }


        /// <summary>
        /// Bind a keyboard key to an action
        /// <param name="button">the button to associate with an action</param>
        /// <param name="action">The action to take when this button is pressed</param>
        /// <param name="flag">0 to overwrite, 1 to append a binding</param>
        /// </summary>
        public void Bind(Keys button, KeyboardInputAction action, int flag)
        {
            //  Remove all old bindings if the flag is set.
            if (flag == 0)
                for (int i = 0; i < m_keyboardBindings.Count; i++)
                    if (m_keyboardBindings[i].Key == button)
                    {
                        m_keyboardBindings.RemoveAt(i);
                        i--;
                    }
            //  Add/append the new binding.
            m_keyboardBindings.Add(new KeyValuePair<Keys, KeyboardInputAction>(button, action));
        }

        /// <summary>
        /// Bind a mouse button to an action
        /// <param name="button">the button to associate with an action</param>
        /// <param name="action">The action to take when this button is pressed</param>
        /// <param name="flag">0 to overwite binding, 1 to append a binding</param>
        /// </summary>
        public void Bind(MouseButtons button, MouseButtonInputAction action, int flag)
        {
            //  Remove all old bindings if the flag is set.
            if (flag == 0)
                for (int i = 0; i < m_mouseBindings.Count; i++)
                    if (m_mouseBindings[i].Key == button)
                    {
                        m_mouseBindings.RemoveAt(i);
                        i--;
                    }
            //  Add/append the new binding.
            m_mouseBindings.Add(new KeyValuePair<MouseButtons, MouseButtonInputAction>(button, action));
        }

        /// <summary>
        /// Unbind a gamepad button
        /// <param name="button">The button to unbind</param>
        /// <param name="action">The action to unbind</param>
        /// </summary>
        public void Unbind(Buttons button, ButtonInputAction action)
        {
            for (int i = 0; i < m_gamePadBindings.Count; i++)
                if (m_gamePadBindings[i].Key == button)
                {
                    m_gamePadBindings.RemoveAt(i);
                    i--;    //  There may be multiple bindings.
                }
        }

        /// <summary>
        /// Unbind a keyboard key
        /// <param name="button">The button to unbind</param>
        /// <param name="action">The action to unbind</param>
        /// </summary>
        public void Unbind(Keys button, KeyboardInputAction action)
        {
            for (int i = 0; i < m_keyboardBindings.Count; i++)
                if (m_keyboardBindings[i].Key == button)
                {
                    m_keyboardBindings.RemoveAt(i);
                    i--;    //  There may be multiple bindings.
                }
        }

        /// <summary>
        /// Unbind a mouse button
        /// <param name="button">The button to unbind</param>
        /// <param name="action">The action to unbind</param>
        /// </summary>
        public void Unbind(MouseButtons button, MouseButtonInputAction action)
        {
            for (int i = 0; i < m_mouseBindings.Count; i++)
                if (m_mouseBindings[i].Key == button)
                {
                    m_mouseBindings.RemoveAt(i);
                    i--;    //  There may be multiple bindings.
                }
        }

        /// <summary>
        /// Bind gamepad analog joystick to an action
        /// <param name="control">The analog control to associate with an action</param>
        /// <param name="action">The action to take when this analog control changes</param>
        /// </summary>
        public void BindAnalog(GamePadAnalogs control, GamePadInputAction action)
        {
            m_gamePadAnalogBindings.Add(new KeyValuePair<GamePadAnalogs, GamePadInputAction>(control, action));
        }

        /// <summary>
        /// Bind mouse movement to an action
        /// <param name="control">The analog control to associate with an action</param>
        /// <param name="action">The action to take when this analog control changes</param>
        /// </summary>
        public void BindAnalog(MouseAnalogs control, MouseInputAction action)
        {
            m_mouseAnalogBindings.Add(new KeyValuePair<MouseAnalogs, MouseInputAction>(control, action));
        }

        /// <summary>
        /// Unbind a gamepad analog joystick movement
        /// <param name="control">The analog control to unbind</param>
        /// <param name="action">The action to unbind</param>
        /// </summary>
        public void UnbindAnalog(GamePadAnalogs control, GamePadInputAction action)
        {
            for (int i = 0; i < m_gamePadAnalogBindings.Count; i++)
                if (m_gamePadAnalogBindings[i].Key.Equals(control))
                {
                    m_gamePadAnalogBindings.RemoveAt(i);
                    i--;
                }
        }

        /// <summary>
        /// Unbind the mouse movement
        /// <param name="control">The analog control to unbind</param>
        /// <param name="action">The action to unbind</param>
        /// </summary>
        public void UnbindAnalog(MouseAnalogs control, MouseInputAction action)
        {
            for (int i = 0; i < m_mouseAnalogBindings.Count; i++)
                if (m_mouseAnalogBindings[i].Key.Equals(control))
                {
                    m_mouseAnalogBindings.RemoveAt(i);
                    i--;
                }
        }

        /// <summary>
        /// The engine's update function should call this function which will check all inputs for changes
        /// </summary>
        public void CheckInputs()
        {

            //  Check GamePad inputs.
            for (int i = 0; i < m_gamePadBindings.Count; i++)
            {
                if (GamePad.GetState(PlayerIndex.One).IsButtonDown(m_gamePadBindings[i].Key) && !m_oldGamePad.IsButtonDown(m_gamePadBindings[i].Key))
                    m_gamePadBindings[i].Value.Invoke(m_gamePadBindings[i].Key, ButtonState.Pressed, PlayerIndex.One);
                if( GamePad.GetState(PlayerIndex.Two).IsButtonDown(m_gamePadBindings[i].Key) && !m_oldGamePad2.IsButtonDown(m_gamePadBindings[i].Key) )
                    m_gamePadBindings[i].Value.Invoke(m_gamePadBindings[i].Key, ButtonState.Pressed, PlayerIndex.Two);
                if( GamePad.GetState(PlayerIndex.Three).IsButtonDown(m_gamePadBindings[i].Key) && !m_oldGamePad3.IsButtonDown(m_gamePadBindings[i].Key) )
                    m_gamePadBindings[i].Value.Invoke(m_gamePadBindings[i].Key, ButtonState.Pressed, PlayerIndex.Three);
                if( GamePad.GetState(PlayerIndex.Four).IsButtonDown(m_gamePadBindings[i].Key) && !m_oldGamePad4.IsButtonDown(m_gamePadBindings[i].Key) )
                    m_gamePadBindings[i].Value.Invoke(m_gamePadBindings[i].Key, ButtonState.Pressed, PlayerIndex.Four);
            }


            //  Check Keyboard inputs.
            for (int i = 0; i < m_keyboardBindings.Count; i++)
                if (Keyboard.GetState().IsKeyDown(m_keyboardBindings[i].Key) && !m_oldKeyboard.IsKeyDown(m_keyboardBindings[i].Key))
                    m_keyboardBindings[i].Value.Invoke(m_keyboardBindings[i].Key, KeyState.Down);
                //else
                //    m_keyboardBindings[i].Value.Invoke(m_keyboardBindings[i].Key, KeyState.Up);

            //  Check Mouse buttons.
            for (int i = 0; i < m_mouseBindings.Count; i++)
            {
                ButtonState bs = Mouse.GetState().MiddleButton;
                //if( Mouse.GetState().MiddleButton 
                if (m_mouseBindings[i].Key == MouseButtons.leftButton)
                    m_mouseBindings[i].Value.Invoke(m_mouseBindings[i].Key, Mouse.GetState().LeftButton);
                else if (m_mouseBindings[i].Key == MouseButtons.middleButton)
                    m_mouseBindings[i].Value.Invoke(m_mouseBindings[i].Key, Mouse.GetState().MiddleButton);
                else if (m_mouseBindings[i].Key == MouseButtons.rightButton)
                    m_mouseBindings[i].Value.Invoke(m_mouseBindings[i].Key, Mouse.GetState().RightButton);
                else if (m_mouseBindings[i].Key == MouseButtons.XButton1)
                    m_mouseBindings[i].Value.Invoke(m_mouseBindings[i].Key, Mouse.GetState().XButton1);
                else if (m_mouseBindings[i].Key == MouseButtons.XButton2)
                    m_mouseBindings[i].Value.Invoke(m_mouseBindings[i].Key, Mouse.GetState().XButton2);
            }

            //  Check Mouse analog.
            for (int i = 0; i < m_mouseAnalogBindings.Count; i++)
                m_mouseAnalogBindings[i].Value.Invoke(m_mouseAnalogBindings[i].Key, Mouse.GetState().X, Mouse.GetState().Y);

            //  Check GamePad analog.
            for (int i = 0; i < m_mouseAnalogBindings.Count; i++)
            {
                if (m_gamePadAnalogBindings[i].Key == GamePadAnalogs.LeftStick)
                    m_gamePadAnalogBindings[i].Value.Invoke(m_gamePadAnalogBindings[i].Key, GamePad.GetState(PlayerIndex.One).ThumbSticks.Left.X, GamePad.GetState(PlayerIndex.One).ThumbSticks.Left.Y, PlayerIndex.One);
                else if (m_gamePadAnalogBindings[i].Key == GamePadAnalogs.RightStick)
                    m_gamePadAnalogBindings[i].Value.Invoke(m_gamePadAnalogBindings[i].Key, GamePad.GetState(PlayerIndex.One).ThumbSticks.Right.X, GamePad.GetState(PlayerIndex.One).ThumbSticks.Right.Y, PlayerIndex.One);

                if( m_gamePadAnalogBindings[i].Key == GamePadAnalogs.LeftStick )
                    m_gamePadAnalogBindings[i].Value.Invoke(m_gamePadAnalogBindings[i].Key, GamePad.GetState(PlayerIndex.Two).ThumbSticks.Left.X, GamePad.GetState(PlayerIndex.Two).ThumbSticks.Left.Y, PlayerIndex.Two);
                else if( m_gamePadAnalogBindings[i].Key == GamePadAnalogs.RightStick )
                    m_gamePadAnalogBindings[i].Value.Invoke(m_gamePadAnalogBindings[i].Key, GamePad.GetState(PlayerIndex.Two).ThumbSticks.Right.X, GamePad.GetState(PlayerIndex.Two).ThumbSticks.Right.Y, PlayerIndex.Two);

                if( m_gamePadAnalogBindings[i].Key == GamePadAnalogs.LeftStick )
                    m_gamePadAnalogBindings[i].Value.Invoke(m_gamePadAnalogBindings[i].Key, GamePad.GetState(PlayerIndex.Three).ThumbSticks.Left.X, GamePad.GetState(PlayerIndex.Three).ThumbSticks.Left.Y, PlayerIndex.Three);
                else if( m_gamePadAnalogBindings[i].Key == GamePadAnalogs.RightStick )
                    m_gamePadAnalogBindings[i].Value.Invoke(m_gamePadAnalogBindings[i].Key, GamePad.GetState(PlayerIndex.Three).ThumbSticks.Right.X, GamePad.GetState(PlayerIndex.Three).ThumbSticks.Right.Y, PlayerIndex.Three);

                if( m_gamePadAnalogBindings[i].Key == GamePadAnalogs.LeftStick )
                    m_gamePadAnalogBindings[i].Value.Invoke(m_gamePadAnalogBindings[i].Key, GamePad.GetState(PlayerIndex.Four).ThumbSticks.Left.X, GamePad.GetState(PlayerIndex.Four).ThumbSticks.Left.Y, PlayerIndex.Four);
                else if( m_gamePadAnalogBindings[i].Key == GamePadAnalogs.RightStick )
                    m_gamePadAnalogBindings[i].Value.Invoke(m_gamePadAnalogBindings[i].Key, GamePad.GetState(PlayerIndex.Four).ThumbSticks.Right.X, GamePad.GetState(PlayerIndex.Four).ThumbSticks.Right.Y, PlayerIndex.Four);
            
            /*else if( m_gamePadAnalogBindings[i].Key == GamePadAnalogs.LeftTrigger )
                m_gamePadAnalogBindings[i].Value.Invoke(m_gamePadAnalogBindings[i], GamePad.GetState(PlayerIndex.One).Triggers.Left.X, GamePad.GetState(PlayerIndex.One).Triggers.Left.);
            else if( m_gamePadAnalogBindings[i].Key == GamePadAnalogs.RightTrigger )
                m_gamePadAnalogBindings[i].Value.Invoke(m_gamePadAnalogBindings[i], GamePad.GetState(PlayerIndex.One).Triggers.Left.X, GamePad.GetState(PlayerIndex.One).Triggers.Left.Y);
            */
            }

            m_oldKeyboard = Keyboard.GetState();
            m_oldMouse = Mouse.GetState();
            m_oldGamePad = GamePad.GetState(PlayerIndex.One);
        }
        #endregion
    }
}
